package com.springboot.frame;

import com.springboot.frame.annotation.SFunction;
import com.springboot.frame.enums.OrderBy;
import com.springboot.frame.enums.SqlLike;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import org.apache.commons.lang3.StringUtils;
import org.apache.ibatis.type.TypeHandlerRegistry;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * 条件查询对象
 *
 * @author liheng
 */
@NoArgsConstructor
public final class Condition<T> {
    /**
     * Mybatis类型处理注册器
     */
    private static final TypeHandlerRegistry TYPE_HANDLER_REGISTRY = new TypeHandlerRegistry();

    /**
     * 查询优先级会用到
     */
    private static final String OPEN = "(";

    private static final String CLOSE = ")";

    private static final String AND = " \nAND ";

    private static final String OR = " \nOR ";
    /**
     * 参数对象
     */
    @Getter
    @Setter
    private T parameterObject;
    /**
     * 查询字段
     */
    @Getter
    @Setter
    private String selectBody;
    /**
     * 分页
     */
    @Getter
    private String limit;
    @Getter
    @Setter
    private boolean distinct = false;
    /**
     * 是否需要逻辑删除,主要针对查询时需要
     */
    @Getter
    @Setter
    private boolean logicDelete = true;
    /**
     * where 过滤条件
     */
    @Getter
    private List<String> where = new ArrayList<>();
    /**
     * 排序容器
     */
    @Getter
    private List<String> orderBy = new ArrayList<>();
    /**
     * 分组容器
     */
    @Getter
    private List<String> groupBy = new ArrayList<>();

    /**
     * 条件参数
     */
    @Getter
    private List<Object> paramCondition = new ArrayList<>();


    /**
     * 并发操作原子类
     * 高并发的情况下，i++无法保证原子性，往往会出现问题，所以引入AtomicInteger类
     */
    private AtomicInteger index = new AtomicInteger();

    public Condition(T parameterObject) {
        this.parameterObject = parameterObject;
    }

    public Condition(T parameterObject, String selectBody) {
        this.parameterObject = parameterObject;
        this.selectBody = selectBody;
    }

    public Condition(T parameterObject, String selectBody, boolean distinct) {
        this.parameterObject = parameterObject;
        this.selectBody = selectBody;
        this.distinct = distinct;
    }

    /**
     * 通过Lambda表达式获取字段名
     *
     * @param fn
     * @param value
     * @return
     */
    public Condition eq(SFunction<T, ?> fn, Object value) {
        eq(ColumnUtils.getName(fn), value);
        return this;
    }

    private Condition eq(String column, Object value) {
        paramCondition.add(value);
        String typeHandlerName = TYPE_HANDLER_REGISTRY.getTypeHandler(value.getClass()).getClass()
                .getName();
        where.add(column + " = #{paramCondition[" + index.getAndIncrement() + "],typeHandler="
                + typeHandlerName + "}");
        return this;
    }


    public Condition neq(String column, Object value) {
        paramCondition.add(value);
        String typeHandlerName = TYPE_HANDLER_REGISTRY.getTypeHandler(value.getClass()).getClass()
                .getName();
        where.add(column + " != #{paramCondition[" + index.getAndIncrement() + "],typeHandler="
                + typeHandlerName + "}");
        return this;
    }

    public Condition gt(String column, Number num) {
        paramCondition.add(num);
        String typeHandlerName = TYPE_HANDLER_REGISTRY.getTypeHandler(num.getClass()).getClass()
                .getName();
        where.add(column + " > #{paramCondition[" + index.getAndIncrement() + "],typeHandler="
                + typeHandlerName + "}");
        return this;
    }


    public Condition ge(String column, Number num) {
        paramCondition.add(num);
        String typeHandlerName = TYPE_HANDLER_REGISTRY.getTypeHandler(num.getClass()).getClass()
                .getName();
        where.add(column + " >= #{paramCondition[" + index.getAndIncrement() + "],typeHandler="
                + typeHandlerName + "}");
        return this;
    }

    public Condition ge(String column, String str) {
        paramCondition.add(str);
        String typeHandlerName = TYPE_HANDLER_REGISTRY.getTypeHandler(str.getClass()).getClass()
                .getName();
        where.add(column + " >= #{paramCondition[" + index.getAndIncrement() + "],typeHandler="
                + typeHandlerName + "}");
        return this;
    }

    public Condition lt(String column, Number num) {
        paramCondition.add(num);
        String typeHandlerName = TYPE_HANDLER_REGISTRY.getTypeHandler(num.getClass()).getClass()
                .getName();
        where.add(column + " < #{paramCondition[" + index.getAndIncrement() + "],typeHandler="
                + typeHandlerName + "}");
        return this;
    }

    public Condition le(String column, Number num) {
        paramCondition.add(num);
        String typeHandlerName = TYPE_HANDLER_REGISTRY.getTypeHandler(num.getClass()).getClass()
                .getName();
        where.add(column + " <= #{paramCondition[" + index.getAndIncrement() + "],typeHandler="
                + typeHandlerName + "}");
        return this;
    }

    public Condition le(String column, String str) {
        paramCondition.add(str);
        String typeHandlerName = TYPE_HANDLER_REGISTRY.getTypeHandler(str.getClass()).getClass()
                .getName();
        where.add(column + " <= #{paramCondition[" + index.getAndIncrement() + "],typeHandler="
                + typeHandlerName + "}");
        return this;
    }

    public Condition between(String column, Number num1, Number num2) {
        paramCondition.add(num1);
        paramCondition.add(num2);
        String typeHandlerName = TYPE_HANDLER_REGISTRY.getTypeHandler(num1.getClass()).getClass()
                .getName();
        where.add(
                column + " BETWEEN " + "#{paramCondition[" + index.getAndIncrement() + "],typeHandler="
                        + typeHandlerName + "}"
                        + " AND "
                        + "#{paramCondition[" + index.getAndIncrement() + "],typeHandler=" + typeHandlerName
                        + "}");
        return this;
    }

    public Condition in(String column, Number... numbers) {
        if (StringUtils.isNotBlank(column) && numbers != null && numbers.length >= 1) {
            String typeHandlerName;
            StringBuilder inValue = new StringBuilder();
            int nowIndex = index.getAndIncrement();
            int innerIndex = 0;
            for (Number number : numbers) {
                typeHandlerName = TYPE_HANDLER_REGISTRY.getTypeHandler(number.getClass())
                        .getClass().getName();
                inValue.append(
                        "#{paramCondition[" + nowIndex + "].innerObj[" + innerIndex + "],typeHandler="
                                + typeHandlerName + "}")
                        .append(",");
                innerIndex++;
            }
            inValue.deleteCharAt(inValue.length() - 1);
            where.add(column + " IN (" + inValue + ")");
        }
        return this;
    }

    public Condition in(String column, List<Integer> vals) {
        if (StringUtils.isNotBlank(column) && vals.size() > 0) {
            where.add(column + " IN (" + StringUtils.join(vals, ",") + ")");
        } else {
            throw new RuntimeException("列值的集合不能为空");
        }
        return this;
    }

    public Condition notIn(String column, Number... numbers) {
        if (StringUtils.isNotBlank(column) && numbers != null && numbers.length >= 1) {
            String typeHandlerName;
            StringBuilder inValue = new StringBuilder();
            int nowIndex = index.getAndIncrement();
            int innerIndex = 0;
            for (Number number : numbers) {
                typeHandlerName = TYPE_HANDLER_REGISTRY.getTypeHandler(number.getClass())
                        .getClass().getName();
                inValue.append(
                        "#{paramCondition[" + nowIndex + "].innerObj[" + innerIndex + "],typeHandler="
                                + typeHandlerName + "}")
                        .append(",");
                innerIndex++;
            }
            inValue.deleteCharAt(inValue.length() - 1);
            where.add(column + " NOT IN (" + inValue + ")");
        }
        return this;
    }


    public Condition notIn(String column, List<Integer> vals) {
        if (StringUtils.isNotBlank(column) && vals.size() > 0) {
            where.add(column + " NOT IN (" + StringUtils.join(vals, ",") + ")");
        } else {
            throw new RuntimeException("列值的集合不能为空");
        }
        return this;
    }

    public Condition isNuLL(String column) {
        if (StringUtils.isNotBlank(column)) {
            where.add(column + " IS NULL");
        }
        return this;
    }


    public Condition isNotNuLL(String column) {
        if (StringUtils.isNotBlank(column)) {
            where.add(column + " IS NOT NULL");
        }
        return this;
    }

    public Condition isEmpty(String column) {
        if (StringUtils.isNotBlank(column)) {
            where.add(column + " = \"\"");
        }
        return this;
    }

    public Condition isNotEmpty(String column) {
        if (StringUtils.isNotBlank(column)) {
            where.add(column + " != \"\"");
        }
        return this;
    }

    public Condition like(String column, Object value) {
        if (StringUtils.isNotBlank(column) && value != null) {
            where.add(column + " LIKE " + handleLike(value, SqlLike.ALL));
        }
        return this;
    }

    public Condition like(String column, Object value, SqlLike sqlLike) {
        if (StringUtils.isNotBlank(column) && value != null) {
            where.add(column + " LIKE " + handleLike(value, sqlLike));
        }
        return this;
    }

    public Condition notLike(String column, Object value) {
        if (StringUtils.isNotBlank(column) && value != null) {
            where.add(column + " NOT LIKE " + handleLike(value, SqlLike.ALL));
        }
        return this;
    }

    public Condition notLike(String column, Object value, SqlLike sqlLike) {
        if (StringUtils.isNotBlank(column) && value != null) {
            where.add(column + " NOT LIKE " + handleLike(value, sqlLike));
        }
        return this;
    }

    public Condition OR() {
        where.add(OR);
        return this;
    }

    public Condition AND() {
        where.add(AND);
        return this;
    }

    public Condition OPEN() {
        where.add(OPEN);
        return this;
    }

    public Condition CLOSE() {
        where.add(CLOSE);
        return this;
    }

    public Condition condition(String condition) {
        where.add(condition);
        return this;
    }

    public Condition order(String column) {
        return order(column, OrderBy.ASC);
    }

    public Condition order(String column, OrderBy value) {
        if (StringUtils.isNotBlank(column) && value != null) {
            orderBy.add(column + " " + handleOrderBy(value));
        }
        return this;
    }

    private String handleOrderBy(OrderBy orderBy) {
        String result;
        switch (orderBy == null ? OrderBy.ASC : orderBy) {
            case DESC:
                result = OrderBy.DESC.name();
                break;
            default:
                result = OrderBy.ASC.name();
                break;
        }
        return result;
    }


    public Condition limit(int offsite) {
        return limit(0, offsite);
    }

    public Condition limit(int index, int offsite) {
        this.limit = " limit " + index + "," + offsite;
        return this;
    }

    public Condition groupBy(String column) {
        if (StringUtils.isNotBlank(column)) {
            groupBy.add(column);
        }
        return this;
    }

    private String handleLike(Object value, SqlLike sqlLike) {
        StringBuilder result = new StringBuilder();
        switch (sqlLike == null ? SqlLike.ALL : sqlLike) {
            case LEFT:
                result.append("%").append(value);
                break;
            case RIGHT:
                result.append(value).append("%");
                break;
            default:
                result.append("%").append(value).append("%");
                break;
        }
        paramCondition.add(result.toString());
        return "#{paramCondition[" + index.getAndIncrement() + "],typeHandler=" + TYPE_HANDLER_REGISTRY
                .getTypeHandler(value.getClass())
                .getClass().getName() + "}";
    }

}
